﻿namespace Mercury.ParticleEngine
{
    using System;
    using System.Globalization;
    using System.Runtime.Serialization;
    using FluentAssertions;
    using Xunit;
    using Xunit.Extensions;

    // ReSharper disable InconsistentNaming

    public class IntervalFTests
    {
        public class Constructor
        {
            [Theory]
            [Trait("Type", "IntervalF")]
            [InlineData(Single.PositiveInfinity, 1f)]
            [InlineData(Single.NegativeInfinity, 1)]
            [InlineData(Single.NaN, 1f)]
            [InlineData(0f, Single.PositiveInfinity)]
            [InlineData(0f, Single.NegativeInfinity)]
            [InlineData(0f, Single.NaN)]
            public void WhenGivenNaNValue_ThrowsArgumentException(Single x, Single y)
            {
                Action invocation = () => new IntervalF(x, y);

                invocation.ShouldThrow<ArgumentException>();
            }

            [Fact]
            [Trait("Type", "IntervalF")]
            public void WhenGivenFiniteValues_CalculatesSize()
            {
                var interval = new IntervalF(0f, 10f);

                interval.Diameter.As<Object>().Should().Be(10f);
            }
        }

        public class DeserializationConstructor
        {
            [Fact]
            [Trait("Type", "IntervalF")]
            public void WhenGivenNullSerializationInfo_ThrowsArgumentNullException()
            {
                Action invocation = () => new IntervalF(null, new StreamingContext());

                invocation.ShouldThrow<ArgumentNullException>();
            }

            [Fact]
            [Trait("Type", "IntervalF")]
            public void WhenGivenSerializationInfo_ReturnsInitializedInterval()
            {
                var serializationInfo = new SerializationInfo(typeof(IntervalF), new FormatterConverter());

                serializationInfo.AddValue("X", 1f);
                serializationInfo.AddValue("Y", 2f);

                var result = new IntervalF(serializationInfo, new StreamingContext());

                result.X.Should<Single>().Be(1f);
                result.Y.Should<Single>().Be(2f);
            }
        }

        public class UnionMethod
        {
            [Fact]
            [Trait("Type", "IntervalF")]
            public void WhenGivenIntervalValues_ReturnsUnion()
            {
                var x = new IntervalF(0f, 5f);
                var y = new IntervalF(5f, 10f);

                var result = IntervalF.Union(x, y);

                result.X.Should<Single>().Be(0f);
                result.Y.Should<Single>().Be(10f);
            }
        }

        public class ContainsMethod
        {
            [Theory]
            [Trait("Type", "IntervalF")]
            [InlineData(Single.PositiveInfinity)]
            [InlineData(Single.NegativeInfinity)]
            [InlineData(Single.NaN)]
            public void WhenGivenNaNValue_ThrowsArgumentException(Single value)
            {
                var target = new IntervalF(0f, 1f);

                Action invocation = () => target.Contains(value);

                invocation.ShouldThrow<ArgumentException>();
            }

            [Theory]
            [Trait("Type", "IntervalF")]
            [InlineData(0f)]
            [InlineData(0.5f)]
            [InlineData(1f)]
            public void WhenGivenValueInsideBounds_ReturnsTrue(Single value)
            {
                var target = new IntervalF(0f, 1f);

                target.Contains(value).Should().BeTrue();
            }

            [Theory]
            [Trait("Type", "IntervalF")]
            [InlineData(-1f)]
            [InlineData(2f)]
            public void WhenGivenValueOutsideBounds_ReturnsFalse(Single value)
            {
                var target = new IntervalF(0f, 1f);

                target.Contains(value).Should().BeFalse();
            }
        }

        public class ParseMethod
        {
            [Fact]
            [Trait("Type", "IntervalF")]
            public void WhenGivenNullValue_ThrowsArgumentNullException()
            {
                Action invocation = () => IntervalF.Parse(null, CultureInfo.InvariantCulture);

                invocation.ShouldThrow<ArgumentNullException>();
            }

            [Fact]
            [Trait("Type", "IntervalF")]
            public void WhenGivenEmptyString_ThrowsArgumentNullException()
            {
                Action invocation = () => IntervalF.Parse(String.Empty, CultureInfo.InvariantCulture);

                invocation.ShouldThrow<ArgumentNullException>();
            }

            [Theory]
            [Trait("Type", "IntervalF")]
            [InlineData("abc")]
            [InlineData("]0.00,1.00]")]
            [InlineData("]0.00,1.00[")]
            [InlineData("[abc,def]")]
            public void WhenGivenBadFormat_ThrowsFormatException(String input)
            {
                Action invocation = () => IntervalF.Parse(input);

                invocation.ShouldThrow<FormatException>();
            }

            [Theory]
            [Trait("Type", "IntervalF")]
            [InlineData("[0.00,1.00]", "de-DE")]
            [InlineData("[0,00.1,00]", "en-GB")]
            public void WhenGivenBadFormatForCulture_ThrowsFormtException(String input, String cultureCode)
            {
                var culture = new CultureInfo(cultureCode);

                Action invocation = () => IntervalF.Parse(input, culture);

                invocation.ShouldThrow<FormatException>();
            }

            [Theory]
            [Trait("Type", "IntervalF")]
            [InlineData("[0.00,1.00]", 0f, 1f)]
            public void WhenGivenGoodFormat_ReturnsNewInterval(String input, Single expectedX, Single expectedY)
            {
                var expected = new IntervalF(expectedX, expectedY);

                var actual = IntervalF.Parse(input);

                actual.Should().Be(expected);
            }

            [Theory]
            [Trait("Type", "IntervalF")]
            [InlineData("[0.00,1.00]", "en-GB", 0f, 1f)]
            [InlineData("[0,00.1,00]", "de-DE", 0f, 1f)]
            public void WhenGivenGoodFormatForCulture_ReturnsNewInterval(String input, String cultureCode, Single expectedX, Single expectedY)
            {
                var culture = new CultureInfo(cultureCode);

                var expected = new IntervalF(expectedX, expectedY);

                var actual = IntervalF.Parse(input, culture);

                actual.Should().Be(expected);
            }
        }

        public class EqualsMethod
        {
            [Fact]
            [Trait("Type", "IntervalF")]
            public void WhenGivenEqualValue_ReturnsTrue()
            {
                var x = new IntervalF(0f, 1f);
                var y = new IntervalF(0f, 1f);

                x.Equals(y).Should().BeTrue();
            }

            [Fact]
            [Trait("Type", "IntervalF")]
            public void WhenGivenDifferentValue_ReturnsFalse()
            {
                var x = new IntervalF(0f, 1f);
                var y = new IntervalF(0f, 2f);

                x.Equals(y).Should().BeFalse();
            }

            [Fact]
            [Trait("Type", "IntervalF")]
            public void WhenGivenEqualValueAsObject_ReturnsTrue()
            {
                var x = new IntervalF(0f, 1f);
                var y = new IntervalF(0f, 1f);

                x.Equals(y as Object).Should().BeTrue();
            }

            [Fact]
            [Trait("Type", "IntervalF")]
            public void WhenGivenDifferentValueAsObject_ReturnsFalse()
            {
                var x = new IntervalF(0f, 1f);
                var y = new IntervalF(0f, 2f);

                x.Equals(y as Object).Should().BeFalse();
            }

            [Fact]
            [Trait("Type", "IntervalF")]
            public void WhenGivenNull_ReturnsFalse()
            {
                var interval = new IntervalF(0f, 1f);

                interval.Equals(null).Should().BeFalse();
            }
        }

        public class GetHashCodeMethod
        {
            [Fact]
            [Trait("Type", "IntervalF")]
            public void WhenObjectsAreSame_YieldsIdenticalHashCode()
            {
                var interval1 = new IntervalF(0f, 1f);
                var interval2 = new IntervalF(0f, 1f);

                interval1.GetHashCode().Should().Be(interval2.GetHashCode());
            }

            [Fact]
            [Trait("Type", "IntervalF")]
            public void WhenObjectsAreDifferent_YieldsDifferentHashCode()
            {
                var interval1 = new IntervalF(0f, 1f);
                var interval2 = new IntervalF(0f, 2f);

                interval1.GetHashCode().Should().NotBe(interval2.GetHashCode());
            }
        }

        public class ToStringMethod
        {
            [Theory]
            [Trait("Type", "IntervalF")]
            [InlineData(0f, 1f, "[0,1]")]
            public void ReturnsCorrectValue(Single x, Single y, String expected)
            {
                var actual = new IntervalF(x, y).ToString();

                actual.Should().Be(expected);
            }

            [Theory]
            [Trait("Type", "IntervalF")]
            [InlineData(0f, 1f, "en-GB", "[0,1]")]
            [InlineData(0f, 1f, "de-DE", "[0.1]")]
            [InlineData(0.5f, 1.5f, "en-GB", "[0.5,1.5]")]
            [InlineData(0.5f, 1.5f, "de-DE", "[0,5.1,5]")]
            public void ReturnsCorrectValueForCulture(Single x, Single y, String cultureCode, String expected)
            {
                var culture = new CultureInfo(cultureCode);

                var actual = new IntervalF(x, y).ToString(culture);

                actual.Should().Be(expected);
            }

            [Theory]
            [Trait("Type", "IntervalF")]
            [InlineData(0f, 1f, "F3", "[0.000,1.000]")]
            public void ReturnsCorrectValueWithFormat(Single x, Single y, String numberFormat, String expected)
            {
                var actual = new IntervalF(x, y).ToString(numberFormat, null);

                actual.Should().Be(expected);
            }
        }

        public class IsDegenerateProperty
        {
            [Fact]
            [Trait("Type", "IntervalF")]
            public void WhenIntervalIsDegenerate_ReturnsTrue()
            {
                var interval = new IntervalF(1f, 1f);

                interval.IsDegenerate.Should().BeTrue();
            }

            [Fact]
            [Trait("Type", "IntervalF")]
            public void WhenIntervalIsProper_ReturnsFalse()
            {
                var interval = new IntervalF(0f, 1f);

                interval.IsDegenerate.Should().BeFalse();
            }
        }

        public class IsProperProperty
        {
            [Fact]
            [Trait("Type", "IntervalF")]
            public void WhenIntervalIsProper_ReturnsTrue()
            {
                var interval = new IntervalF(0f, 1f);

                interval.IsProper.Should().BeTrue();
            }

            [Fact]
            [Trait("Type", "IntervalF")]
            public void WhenIntervalIsDegenerate_ReturnsFalse()
            {
                var interval = new IntervalF(1f, 1f);

                interval.IsProper.Should().BeFalse();
            }
        }

        public class InteriorProperty
        {
            [Fact]
            [Trait("Type", "IntervalF")]
            public void ReturnsIntervalWithLargerLowerBound()
            {
                var interval = new IntervalF(0f, 1f);

                interval.Interior.X.Should().BeGreaterThan(interval.X);
            }

            [Fact]
            [Trait("Type", "IntervalF")]
            public void ReturnsIntervalWithSmallerUpperBound()
            {
                var interval = new IntervalF(0f, 1f);

                interval.Interior.Y.Should().BeLessThan(interval.Y);
            }
        }

        public class ClosureProperty
        {
            [Fact]
            [Trait("Type", "IntervalF")]
            public void ReturnsIntervalWithSmallerLowerBound()
            {
                var interval = new IntervalF(0f, 1f);

                interval.Closure.X.Should().BeLessThan(interval.X);
            }

            [Fact]
            [Trait("Type", "IntervalF")]
            public void ReturnsIntervalWithLargerUpperBound()
            {
                var interval = new IntervalF(0f, 1f);

                interval.Closure.Y.Should().BeGreaterThan(interval.Y);
            }
        }

        public class EqualityOperator
        {
            [Fact]
            [Trait("Type", "IntervalF")]
            public void WhenGivenEqualValue_ReturnsTrue()
            {
                var interval1 = new IntervalF(0f, 1f);
                var interval2 = new IntervalF(0f, 1f);

                (interval1 == interval2).Should().BeTrue();
            }

            [Fact]
            [Trait("Type", "IntervalF")]
            public void WhenGivenDifferentValue_ReturnsFalse()
            {
                var interval1 = new IntervalF(0f, 1f);
                var interval2 = new IntervalF(0f, 2f);

                (interval1 == interval2).Should().BeFalse();
            }
        }

        public class InequalityOperator
        {
            [Fact]
            [Trait("Type", "IntervalF")]
            public void WhenGivenEqualValue_ReturnsFalse()
            {
                var interval1 = new IntervalF(0f, 1f);
                var interval2 = new IntervalF(0f, 1f);

                (interval1 != interval2).Should().BeFalse();
            }

            [Fact]
            [Trait("Type", "IntervalF")]
            public void WhenGivenDifferentValue_ReturnsTrue()
            {
                var interval1 = new IntervalF(0f, 1f);
                var interval2 = new IntervalF(0f, 2f);

                (interval1 != interval2).Should().BeTrue();
            }
        }

        public class GetObjectDataMethod
        {
            [Fact]
            [Trait("Type", "IntervalF")]
            public void WhenGivenSerializationInfo_PopulatesIt()
            {
                ISerializable instance = new IntervalF(1f, 10f);

                var serializationInfo = new SerializationInfo(typeof(IntervalF), new FormatterConverter());

                instance.GetObjectData(serializationInfo, new StreamingContext());

                serializationInfo.GetSingle("X").Should<Single>().Be(1f);
                serializationInfo.GetSingle("Y").Should<Single>().Be(10f);
            }
        }
    }

    // ReSharper restore InconsistentNaming
}